#include "GLRenderer.h"
#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include "GLShaders.h"
#include "GLYuvTexture.h"
#include "GLRgbTexture.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"

GLuint program;
GLuint vao;
#define VBO_SIZE   2
GLuint vbo[VBO_SIZE];
GLuint ebo;
bool isYuv;
YuvTexture *yuvTexture;
RgbTexture *rgbTexture;

const char* vsSource = "#version 330 core\n"
                       "layout (location = 0) in vec2 a_position;\n"
                       "layout (location = 2) in vec2 a_texcoord;\n"
                       "uniform mat4 proj;\n"
                       "uniform mat4 view;\n"
                       "out vec4 v_color;\n"
                       "out vec2 v_texcoord;\n"
                       "void main(){\n"
                       "  gl_Position = proj * view * vec4(a_position, 0, 1);\n"
                       "  v_texcoord = a_texcoord;\n"
                       "}";
const char* fsYuvSource = "#version 330 core\n"
                          "in vec2 v_texcoord;\n"
                          "uniform sampler2D tex_y;\n"
                          "uniform sampler2D tex_uv;\n"
                          "out vec4 FragColor;\n"
                          "void main(){\n"
                          "    vec3 yuv;\n"
                          "    yuv.x = texture(tex_y, v_texcoord).r;\n"
                          "    yuv.y = texture(tex_uv, v_texcoord).g - 0.5;\n"
                          "    yuv.z = texture(tex_uv, v_texcoord).r - 0.5;"
                          "    //highp vec3 rgb = mat3( 1,       1,      1,        \n"     //BT.601
                          "    //                       0,     -0.3455,  1.779,    \n"
                          "    //                       1.4075, -0.7169,  0) * yuv; \n"

                          "    //highp vec3 rgb = mat3( 1,       1,      1,        \n"      //BT.709
                          "    //                       0,     -0.1868,  1.856,    \n"
                          "    //                       1.5478, -0.4680,  0) * yuv; \n"

                          "    highp vec3 rgb = mat3( 1,       1,      1,        \n"        // BT.2020
                          "                           0,     -0.1645,  1.8814,    \n"
                          "                           1.4746, -0.5713,  0) * yuv; \n"

                          "    FragColor = vec4(rgb, 1);"
                          "}";

const char* fsRgbSource = "#version 330 core\n"
                          "in vec2 v_texcoord;\n"
                          "uniform sampler2D tex_y;\n"
                          "out vec4 FragColor;\n"
                          "void main(){\n"
                          "    FragColor = texture(tex_y, v_texcoord);"
                          "}";

void GLRenderer::gl_init(bool isYuv, const std::string &yuvFile, const std::string &rgbFile){
    // 3.glad管理函数指针
    bool bLoadSuc = gladLoadGLLoader((GLADloadproc) glfwGetProcAddress);
    if(!bLoadSuc){
        std::cout << "Failed load glProcAddress!" << std::endl;
        return;
    }

    if(isYuv){
        yuvTexture = new YuvTexture(yuvFile, 1600, 1600);
        yuvTexture->read();
        program = getProgramFromShaderSource(vsSource, fsYuvSource);
    } else {
        rgbTexture = new RgbTexture(rgbFile, 1600, 1600);
        program = getProgramFromShaderSource(vsSource, fsRgbSource);
    }
    ::isYuv = isYuv;
    gl_initMesh();
}

void GLRenderer::gl_initMesh() {
    float vertexPos[] = {
            -0.5f, 0.5f,
            -0.5f, -0.5f,
            0.5f, -0.5f,
            0.5f, 0.5f,
    };

    float vertexTexcoord[] = {
            0, 0,
            0, 1,
            1, 1,
            1, 0
    };

    int indices[] = {
            0, 1, 2,
            0, 2, 3
    };

    glGenVertexArrays(1, &vao);
    glGenBuffers(VBO_SIZE, vbo);
    glGenBuffers(1, &ebo);

    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPos), vertexPos, GL_STATIC_DRAW);
    GLuint  loc = glGetAttribLocation(program, "a_position");
    glEnableVertexAttribArray(loc);
    glVertexAttribPointer(loc, 2, GL_FLOAT, false, 0, (void*) 0);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexTexcoord), vertexTexcoord, GL_STATIC_DRAW);
    loc = glGetAttribLocation(program, "a_texcoord");
    glEnableVertexAttribArray(loc);
    glVertexAttribPointer(loc, 2, GL_FLOAT, false, 0, (void*) 0);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glBindVertexArray(0);
}

void GLRenderer::gl_render(uint32_t width, uint32_t height, bool isPlaying){
    glViewport(0, 0, width, height);
    glScissor(0, 0, width, height);
    glClearColor(0.1f, 0.2f, 0.3f, 0.5f);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(program);
    glm::vec3 cameraPos = { 0, 0, -1 };
    glm::mat4 viewMat = glm::lookAt(glm::vec3(0, 0, 0), cameraPos, glm::vec3{ 0.f, 1.f, 0.f });
    glUniformMatrix4fv(glGetUniformLocation(program, "proj"), 1, GL_FALSE, glm::value_ptr(glm::mat4(1.f)));
    glUniformMatrix4fv(glGetUniformLocation(program, "view"), 1, GL_FALSE, glm::value_ptr(viewMat));

    if(isYuv){
        if(isPlaying) {
            yuvTexture->nextFrame();
        }
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, yuvTexture->texYId);
        glUniform1i(glGetUniformLocation(program, "tex_y"), 0);

        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, yuvTexture->texUVId);
        glUniform1i(glGetUniformLocation(program, "tex_uv"), 1);
    } else {
        if(isPlaying) {
            rgbTexture->nextFrame();
        }
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, rgbTexture->texYId);
        glUniform1i(glGetUniformLocation(program, "tex_y"), 0);
    }

    glBindVertexArray(vao);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glPointSize(5.0f);

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);
}

void GLRenderer::gl_destroy(){
    if(isYuv){
        yuvTexture->destroy();
        delete yuvTexture;
    } else {
        rgbTexture->destroy();
        delete rgbTexture;
    }
    glDeleteBuffers(1, &ebo);
    glDeleteBuffers(VBO_SIZE, vbo);
    glDeleteVertexArrays(1, &vao);
    glDeleteProgram(program);
}
